﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlServerCe;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OverStore.Configuration;
using OverStore.Configuration.Builder.Fluent;
using OverStore.Runtime.OverStoreContext;
using OverStore.Runtime.Storage;
using OverStore.Configuration.Storage;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Configuration.Components;
using OverStore.Configuration.Builder;
using OverStore.Sql.TableSchema;
using System.Reflection;
using OverStore.Scoping.Manager;
using OverStore.Scoping;
using OverStore.Sql;
using OverStore.Runtime.ObjectRepository.Mapping;
using OverStore.Runtime.ObjectRepository.CommandSet;
using OverStore.Configuration.ObjectRepository.Mapping;
using OverStore.Configuration.ObjectRepository.Depot;

namespace OverStore.TutorialAsTest.Application1
{
    /*
     * Some notes about tests.     
     * There are few ways to configure same actions.
     * One provide simple and slow way, other 
     * provide fast but more complex way to configure. 
     * 
     * The slow configuration uses reflection. Reflection is 
     * not very fast thing, but it allows create mapper, depots,
     * table structures based on Public contract (public properties)
     * of classes.
     * 
     * Fast configuration uses delegates for mapping and identifier management.
     * The fastest OverStore test approximately 2-3 times slower than 'handmade' mapping.
     * 
     * The result on my pc is (mapping 99999 objects)
     * 
     * Handmade mapping :       00:00:00.0960129	
     * Resultset, Delegate:     00:00:00.1444304	
     * Keyset, Delegate:        00:00:00.2866416	
     * 
     * 
     * But you should take into attention that 
     */

    [TestClass]
    public class ObjectReaderPerformanceTest
    {
        /******
         * Here we will measure performance of object reading.
         * Object reading considered as fastest way to access stored data.
         * 
         * Here is draft description of object reader algorithm:
         * 
         *  Map Parameters -> Create Command -> Execute Reader ->
         *      For each record ->
         *          Get ReadIdentifier -> Get item from repository ->
         *   --> Check is Item in map <- return -> Create with key -> Put into map -> Fill with Data <- return
         * 
         * So, in any way object reader works via standard Object repository gateway, and 
         * we also can meause performace of access by identifier.
         * 
         * We avoid using database for measure of own OverStore performance.
         */

        /// <summary>
        /// Class for item used for test. It is simple flat POCO.
        /// </summary>
        class TestItem
        {
            public int Id { get; set; }

            public string Name { get; set; }

            public string Description { get; set; }

            public int Type { get; set; }
        }

        class DataStorage : IDataStorage, IComponentConfiguration<IDataStorage>
        {
            private SqlFormatting _Formatting = new SqlServerCeFormatting();

            private OperationScopeManager _ScopeManager = new OperationScopeManager();

            #region IDataStorageConfiguration Members

            public IDataStorage CreateComponent(IOverStoreContext context)
            {
                return new DataStorage();
            }

            #endregion

            #region IDataStorage Members

            public TypedRecord Execute(ICommand command, IRecord parameters, out int rowsAffected)
            {
                var c = command as Command;
                if (c != null)
                    return c.ExecuteRead(parameters, out rowsAffected);
                else
                    throw new InvalidOperationException();
            }

            public IEnumerable<TypedRecord> ReadAll(ICommand command, IRecord parameters)
            {
                var c = command as Command;
                if (c != null)
                    return c.ExecuteReader(parameters);
                else
                    throw new InvalidOperationException();
            }

            public bool Disposed { get { return false; } }

            public OperationScope OpenScope()
            {
                var r = new OperationScope();
                _ScopeManager.StartOperationScope(r);
                return r;
            }

            public OverStore.Sql.SqlFormatting SqlFormatting
            {
                get { return _Formatting; }
            }

            #endregion
        }

        /// <summary>
        /// Commands produces set of mock data.
        /// </summary>
        class Command : ICommand
        {
            class InnerRecord : IRecord
            {
                private readonly int _Id;

                public InnerRecord(int id)
                {
                    _Id = id;
                }

                public bool TryGetValue(string name, out object result)
                {
                    switch (name)
                    {
                        case "Id":
                            {
                                result = _Id;
                                return true;
                            }
                        case "Name":
                            {
                                result = "TestName";
                                return true;
                            }
                        case "Description":
                            {
                                result = "Test descritpion";
                                return true;
                            }
                        case "Type":
                            {
                                result = 1;
                                return true;
                            }
                    }
                    result = null;
                    return false;
                }
            }

            public TypedRecord ExecuteRead(IRecord parameters, out int rowsAffected)
            {
                rowsAffected = 1;
                object id;
                parameters.TryGetValue("Id", out id);

                return new WrapperTypedRecord(new InnerRecord((int)id));
            }

            public IEnumerable<TypedRecord> ExecuteReader(IRecord parameters)
            {
                for (int i = 1; i < 100000; i++)
                    yield return new WrapperTypedRecord(new InnerRecord(i));
            }
        }

        class CommandConfig : IComponentConfiguration<ICommand>
        {
            public ICommand CreateComponent(IOverStoreContext context)
            {
                return new Command();
            }
        }

        private static T GetValue<T>(IRecord rec, string name)
        {
            object result;
            rec.TryGetValue(name, out result);
            return (T)result;
        }

        [TestMethod]
        public void HandmadeMapping()
        {
            var session = Config_ResultsetReader_DelegateMapper_DelegateDepot().CreateSession();
            Dictionary<int, TestItem> map = new Dictionary<int, TestItem>();
            foreach (var record in new Command().ExecuteReader(EmptyTypedRecord.Value))
            {
                var item = new TestItem
                {
                    Id = GetValue<int>(record, "Id"),
                    Name = GetValue<String>(record, "Name"),
                    Description = GetValue<string>(record, "Description"),
                    Type = GetValue<int>(record, "Type")
                };
                map.Add(item.Id, item);
            }
        }

        static IComponentConfiguration<IMapper<TestItem>> CreateTestItemMapper()
        {
            return Component.Mapper.Delegate<TestItem>(
                (item, name, p) =>
                {
                    if (name == "Id")
                        return item.Id;
                    if (name == "Name")
                        return item.Name;
                    if (name == "Description")
                        return item.Description;
                    if (name == "Type")
                        return item.Type;
                    return Missing.Value;
                },
                (data, item, p) =>
                {
                    item.Name = data.GetString("Name");
                    item.Type = data.GetInt32("Type");
                    item.Description = data.GetString("Description");
                });
        }

        static IComponentConfiguration<ICommandSet> CreateTestItemCommandSet()
        {
            var cs = Component.CommandSet.FromTable(
                    Component.CreateTable("TestItem")
                            .Key<int>("Id")
                            .Column<string>("Name")
                            .Column<string>("Description")
                            .Column<int>("Type"), false);
            cs.CustomSelect = new CommandConfig();
            return cs;
        }

        static OverStoreConfiguration Config_KeysetReader_ReflectionMapper_ReflectionDepot()
        {
            return OverStoreFluent.Configure()
                .LogToConsole().WithDataStorage(new DataStorage())
                .CustomClassRepository<TestItem, int>()
                    .WithDepot(Component.Depot.WithInternalKey<TestItem, int>("Id"))
                    .WithMapper(CreateTestItemMapper())
                    .WithCommands(CreateTestItemCommandSet())
                    .ByIdentifierAttaching(i => i.Id)
                    .ParameterlessReader(false)
                        .WithCommand(new CommandConfig())
                        .WithIdentifierProvider(Component.IdentifierProvider.Delegate<int>(r => r.GetInt32("Id")))
                    .EndReader()
                .EndObjectRepository()
            .End();
        }

        /// <summary>
        /// Measures performance of slowest variant.
        /// </summary>
        [TestMethod]
        public void Reader_Keyset_RelfectionMapper_ReflectionDepot()
        {
            var session = Config_KeysetReader_ReflectionMapper_ReflectionDepot().CreateSession();
            int cnt = session.CreateReader<TestItem>().Count();
        }

        static OverStoreConfiguration Config_ResultsetReader_ReflectionMapper_ReflectionDepot()
        {
            return OverStoreFluent.Configure()
                .LogToConsole().WithDataStorage(new DataStorage())
                .CustomClassRepository<TestItem, int>()
                    .WithDepot(Component.Depot.WithInternalKey<TestItem, int>("Id"))
                    .WithMapper(CreateTestItemMapper())
                    .WithCommands(CreateTestItemCommandSet())
                    .ByIdentifierAttaching(i => i.Id)
                    .ParameterlessReader(true)
                        .WithCommand(new CommandConfig())
                        .WithIdentifierProvider(Component.IdentifierProvider.Delegate<int>(r => r.GetInt32("Id")))
                    .EndReader()
                .EndObjectRepository()
            .End();
        }

        /// <summary>
        /// Measures performance of slowest variant.
        /// </summary>
        [TestMethod]
        public void Reader_Resultset_RelfectionMapper_ReflectionDepot()
        {
            var session = Config_ResultsetReader_ReflectionMapper_ReflectionDepot().CreateSession();
            int cnt = session.CreateReader<TestItem>().Count();
        }

        static int GetTestItemKey(TestItem item)
        {
            return item.Id;
        }

        static TestItem CreateTestItemWithKey()
        {
            return new TestItem();
        }

        sealed class TestItemMapper : MapperBase<TestItem>
        {
            protected sealed override object GetObjectDataCore(TestItem instance, string name, IRegistry registry)
            {
                var sc = StringComparer.OrdinalIgnoreCase;
                if (sc.Equals(name, "Id"))
                    return instance.Id;
                if (sc.Equals(name, "Name"))
                    return instance.Name;
                if (sc.Equals(name, "Description"))
                    return instance.Description;
                if (sc.Equals(name, "Type"))
                    return instance.Type;
                return null;
            }

            protected sealed override void ApplyObjectDataCore(TypedRecord data, TestItem result, IRegistry registry)
            {
                result.Id = data.GetValue<int>("Id");
                result.Name = data.GetValue<string>("Name");
                result.Type = data.GetValue<int>("Type");
                result.Description = data.GetValue<string>("Description");
            }
        }

        static object GetTestItemData(TestItem item, string name, IPersistenceServiceProvider locator)
        {
            var sc = StringComparer.OrdinalIgnoreCase;
            if (sc.Equals(name, "Id"))
                return item.Id;
            if (sc.Equals(name, "Name"))
                return item.Name;
            if (sc.Equals(name, "Description"))
                return item.Description;
            if (sc.Equals(name, "Type"))
                return item.Type;
            return null;
        }

        static void ApplyTestItemData(IRecord data, TestItem item, IPersistenceServiceProvider registryLocator)
        {
            item.Id = data.GetValue<int>("Id");
            item.Name = data.GetValue<string>("Name");
            item.Type = data.GetValue<int>("Type");
            item.Description = data.GetValue<string>("Description");
        }

        static OverStoreConfiguration Config_ResultsetReader_DelegateMapper_DelegateDepot()
        {
            return OverStoreFluent.Configure()
                .NoLogging().WithDataStorage(new DataStorage())
                .CustomClassRepository<TestItem, int>()
                    .WithDepot(Component.Depot.WithInternalKey<TestItem, int>(GetTestItemKey, CreateTestItemWithKey))
                    .WithMapper(new TestItemMapper())
                    .WithCommands(CreateTestItemCommandSet())
                    .ByIdentifierAttaching(i => i.Id)
                    .ParameterlessReader(true)
                        .WithCommand(new CommandConfig())
                        .WithIdentifierProvider(Component.IdentifierProvider.Delegate<int>(r => r.GetInt32("Id")))
                    .EndReader()
                .EndObjectRepository()
            .End();
        }

        [TestMethod]
        public void Reader_ResultsetReader_DelegateMapper_DelegateDepot()
        {
            var session = Config_ResultsetReader_DelegateMapper_DelegateDepot().CreateSession();
            int cnt = session.CreateReader<TestItem>().Count();
            Assert.AreEqual(99999, cnt);
        }

        static OverStoreConfiguration Config_KeysetReader_DelegateMapper_DelegateDepot()
        {
            return OverStoreFluent.Configure()
                .NoLogging().WithDataStorage(new DataStorage())
                .CustomClassRepository<TestItem, int>()
                    .WithDepot(Component.Depot.WithInternalKey<TestItem, int>(GetTestItemKey, CreateTestItemWithKey))
                    .WithMapper(new TestItemMapper())
                    .WithCommands(CreateTestItemCommandSet())
                    .ByIdentifierAttaching(i => i.Id)
                    .ParameterlessReader(false)
                        .WithCommand(new CommandConfig())
                        .WithIdentifierProvider(Component.IdentifierProvider.Delegate<int>(r => r.GetInt32("Id")))
                    .EndReader()
                .EndObjectRepository()
            .End();
        }

        [TestMethod]
        public void Reader_KeysetReader_DelegateMapper_DelegateDepot()
        {
            var session = Config_ResultsetReader_DelegateMapper_DelegateDepot().CreateSession();
            int cnt = session.CreateReader<TestItem>().Count();
            Assert.AreEqual(99999, cnt);
        }

        class Deal
        {
            public Guid Id { get; set; }

            public string Description { get; set; }

            public DateTime Date { get; set; }

            public Guid Item { get; set; }

            public decimal Quantity { get; set; }

            public decimal Cost { get; set; }

            public Guid SourcePlace { get; set; }

            public Guid TargetPlace { get; set; }

            public Guid Actor { get; set; }

            public Guid Contractor { get; set; }

            public Guid Batch { get; set; }

            public Guid Contract { get; set; }

            public Guid CreatedBy { get; set; }

            public DateTime CreatedDate { get; set; }
        }

        static void ApplyDealData(TypedRecord data, Deal target, IRegistry locator)
        {
            target.Id = data.GetGuid("Id");
            target.Actor = data.GetGuid("Actor");
            target.Batch = data.GetGuid("Batch");
            target.Contract = data.GetGuid("Contract");
            target.Contractor = data.GetGuid("Contractor");
            target.Cost = data.GetDecimal("Cost");
            target.CreatedBy = data.GetGuid("CreatedBy");
            target.CreatedDate = data.GetDateTime("CreatedDate");
            target.Date = data.GetDateTime("Date");
            target.Description = data.GetString("Description");
            target.Item = data.GetGuid("Item");
            target.Quantity = data.GetDecimal("Quantity");
            target.SourcePlace = data.GetGuid("SourcePlace");
            target.TargetPlace = data.GetGuid("TargetPlace");
        }

        static object GetDealData(Deal instance, string name, IRegistry registry)
        {
            if (StringComparer.OrdinalIgnoreCase.Equals("orig__oid", name))
                return registry.GetIdentifier<Deal, Guid>(instance);
            if (StringComparer.OrdinalIgnoreCase.Equals("Id", name))
                return instance.Id;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "Actor"))
                return instance.Actor;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "Batch"))
                return instance.Batch;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "Contract"))
                return instance.Contract;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "Contractor"))
                return instance.Contractor;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "Cost"))
                return instance.Cost;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "CreatedBy"))
                return instance.CreatedBy;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "CreatedDate"))
                return instance.CreatedDate;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "Date"))
                return instance.Date;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "Description"))
                return instance.Description;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "Item"))
                return instance.Item;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "Quantity"))
                return instance.Quantity;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "SourcePlace"))
                return instance.SourcePlace;
            if (StringComparer.OrdinalIgnoreCase.Equals(name, "TargetPlace"))
                return instance.TargetPlace;
            return null;
        }

        static IComponentConfiguration<ICommandSet> CreateDealCommandSet()
        {
            return Component.CommandSet.FromTable(
                    Component.CreateTable("Deal")
                        .Key<Guid>("Id")
                        .Column<String>("Description")
                        .Column<DateTime>("Date")
                        .Column<Guid>("Item")
                        .Column<Decimal>("Quantity")
                        .Column<Decimal>("Cost")
                        .Column<Guid>("SourcePlace")
                        .Column<Guid>("TargetPlace")
                        .Column<Guid>("Actor")
                        .Column<Guid>("Contractor")
                        .Column<Guid>("Batch")
                        .Column<Guid>("Contract")
                        .Column<Guid>("CreatedBy")
                        .Column<DateTime>("CreatedDate"),
                    false);
        }

        static OverStoreConfiguration CreateDealConfig()
        {
            return OverStoreFluent.Configure()
                .LogToConsole().ConnectToDatabase<SqlCeConnection>(TestHelper.MyMoneyConnectionString, ProcessOpenedReadersMode.ForciblyCloseReaders)
                .CustomClassRepository<Deal, Guid>()
                    .WithDepot(new DelegateDepotConfiguration<Deal, Guid>
                               {
                                   StoreIdentifierCopy = (instance, id) => instance.Id = id,
                                   CreateInstanceFunction = () => new Deal(),
                                   IdentifierGenerator = Component.IdentifierGenerator.Guid<Deal>()
                               })
                    .WithMapper(new DelegateMapperConfiguration<Deal> { ApplyObjectDataFunction = ApplyDealData, GetObjectDataFunction = GetDealData })
                    .WithCommands(CreateDealCommandSet())
                    .ByIdentifierAttaching(i => i.Id)
                    .ParameterlessReader(true)
                        .WithCommand(Component.AdoCommand.ReadCommand("select * from Deal"))
                        .WithIdentifierProvider(Component.IdentifierProvider.Delegate<Guid>(r => r.GetGuid("Id")))
                    .EndReader()
                .EndObjectRepository()
            .End();
        }

        [TestMethod]
        public void ReadDealTableWithDbReader()
        {
            using (var dbConnection = new SqlCeConnection(TestHelper.MyMoneyConnectionString))
            using (var command = dbConnection.CreateCommand())
            {
                command.CommandText = "Select * from Deal";
                command.CommandType = CommandType.Text;
                dbConnection.Open();
                var list = new List<Deal>();
                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var target = new Deal();
                        target.Id = reader.GetGuid(reader.GetOrdinal("Id"));
                        target.Actor = reader.GetGuid(reader.GetOrdinal("Actor"));
                        target.Batch = reader.GetValue<Guid>("Batch");
                        target.Contract = reader.GetValue<Guid>("Contract");
                        target.Contractor = reader.GetValue<Guid>("Contractor");
                        target.Cost = reader.GetValue<Decimal>("Cost");
                        target.CreatedBy = reader.GetValue<Guid>("CreatedBy");
                        target.CreatedDate = reader.GetValue<DateTime>("CreatedDate");
                        target.Date = reader.GetValue<DateTime>("Date");
                        target.Description = reader.GetValue<string>("Description");
                        target.Item = reader.GetValue<Guid>("Item");
                        target.Quantity = reader.GetValue<decimal>("Quantity");
                        target.SourcePlace = reader.GetValue<Guid>("SourcePlace");
                        target.TargetPlace = reader.GetValue<Guid>("TargetPlace");
                        list.Add(target);
                    }
                }
            }
        }

        [TestMethod]
        public void ReadDealTableWithOverstore()
        {
            using (var session = CreateDealConfig().CreateSession())
            {
                var dealList = session.CreateReader<Deal>().ToList();
            }
            //Assert.AreEqual(6475, dealList.Count);
        }
    }
}
